﻿
package extremefx.i18n {
	import extremefx.DateTime;
	import extremefx.DayOfWeek;
	import extremefx.i18n.IFormatProvider;	

	/**
	 * @author Marcelo Volmaro
	 */
	public class DateTimeFormatInfo implements IFormatProvider {
		private static var _dummyDate:DateTime = new DateTime();
		private static var _invInfo:DateTimeFormatInfo;
		
		/**
		 * Invariant default values
		 */
		private static const _SHORT_DAY_NAMES:Array = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
		private static const _ABBR_DAY_NAMES:Array = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
		private static const _DAY_NAMES:Array = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
		private static const _ABBR_MONTH_NAMES:Array = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ""];
		private static const _MONTH_NAMES:Array = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", ""];
		internal static const _ERA_NAMES:Array = ["A.D."];

		public var amDesignator:String;
		public var pmDesignator:String;
		public var dateSeparator:String;
		public var timeSeparator:String;
		public var shortDatePattern:String;
		public var longDatePattern:String;
		public var shortTimePattern:String;
		public var longTimePattern:String;
		public var monthDayPattern:String;
		public var yearMonthPattern:String;
		
		protected var _fullDateTimePattern:String;
		protected var _RFC1123Pattern:String;
		protected var _sortableDateTimePattern:String;
		protected var _universalSortableDateTimePattern:String;
		protected var _firstDayOfWeek:uint;
		
		public var calendar:Calendar;
		public var calendarWeekRule:uint;
		
		protected var _abbreviatedDayNames:Array;
		protected var _abbreviatedMonthNames:Array;
		protected var _dayNames:Array;
		protected var _monthNames:Array;
		public var genitiveAbbreviatedMonthNames:Array;
		protected var _shortDayNames:Array;
		protected var genitiveMonthNames:Array;
		protected var _nativeCName:String;
		
		protected var _upperAbbrMonths:Array;
		protected var _upperMonths:Array;
		protected var _upperDays:Array;
		protected var _upperAbbrDays:Array;
		
		internal var _parseRegExp:Object;

		public function DateTimeFormatInfo(...args) {
			amDesignator = "AM";
			pmDesignator = "PM";
			dateSeparator = "/";
			timeSeparator = ":";
			shortDatePattern = "MM/dd/yyyy";
			longDatePattern = "dddd, dd MMMM yyyy";
			shortTimePattern = "HH:mm";
			longTimePattern = "HH:mm:ss";
			monthDayPattern = "MMMM dd";
			yearMonthPattern = "yyyy MMMM";
			_fullDateTimePattern = "dddd, dd MMMM yyyy HH:mm:ss";
			_RFC1123Pattern = "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'"; 
			_sortableDateTimePattern = "yyyy'-'MM'-'dd'T'HH':'mm':'ss";
			_universalSortableDateTimePattern = "yyyy'-'MM'-'dd HH':'mm':'ss'Z'";

			_firstDayOfWeek = DayOfWeek.SUNDAY;
			calendar = new GregorianCalendar();
			calendarWeekRule = CalendarWeekRule.FIRST_DAY;

			_abbreviatedDayNames = _ABBR_DAY_NAMES.concat();
			_dayNames = _DAY_NAMES.concat();
			_abbreviatedMonthNames = _ABBR_MONTH_NAMES.concat();
			_monthNames = _MONTH_NAMES.concat();

			genitiveAbbreviatedMonthNames = _ABBR_MONTH_NAMES.concat();
			genitiveMonthNames = _MONTH_NAMES.concat();
			_shortDayNames = _SHORT_DAY_NAMES.concat();
			_nativeCName = "Gregorian Calendar";
			
			if (args.length == 1){
				var o:Object = args[0];
				for (var key:String in o){
					this[key] = o[key];
				} 
			}
			
			_parseRegExp = {};
		}
		
		public function getFormat(pType:*):* {
			if (pType is Date || pType is DateTime) return this;
			return null;
		}
		
		public static function getInstance(pProvider:IFormatProvider):DateTimeFormatInfo {
			if (pProvider != null) {
				var dtfi:DateTimeFormatInfo = DateTimeFormatInfo(pProvider.getFormat(_dummyDate));
				if (dtfi != null)
					return dtfi;
			}
			
			return currentInfo;
		}
		
		public function getAbbreviatedEraName(pEra:int):String {
			if (pEra < 0 || pEra >= calendar.abbreviatedEraNames.length)
				throw new RangeError ("pEra", pEra.toString());
			return calendar.abbreviatedEraNames[pEra];
		}

		public function getAbbreviatedMonthName(pMonth:int):String {
			if (pMonth < 1 || pMonth > 13) throw new RangeError();
			return _abbreviatedMonthNames[pMonth - 1];
		}
		
		public function getEra(pEraName:String):int {
			if (pEraName == null) throw new ArgumentError ();
			
			pEraName = pEraName.toLowerCase();
			
			var eras:Array = calendar.eraNames;
			var i:int = 0, en:String;
			while (en = eras[i++]){
				if (en.toLowerCase() == pEraName) return calendar.eras[i-1];
			}
			
			eras = calendar.abbreviatedEraNames;
			i = 0;
			while (en = eras[i++]){
				if (en.toLowerCase() == pEraName) return calendar.eras[i-1];
			}

			return -1;
		}
		
		public function getEraName(pEra:int):String {
			if (pEra < 0 || pEra > calendar.eraNames.length) throw new RangeError ("pEra", pEra.toString());
			return calendar.eraNames[pEra - 1];
		}

		public function getMonthName(pMonth:int):String {
			if (pMonth < 1 || pMonth > 13) throw new RangeError();
			return _monthNames[pMonth-1];
		}
		
		public function get abbreviatedDayNames():Array {
			return _abbreviatedDayNames.concat();
		}
		
		public function set abbreviatedDayNames(pDayNames:Array):void {
			if (pDayNames == null) throw new ArgumentError();
			if (pDayNames.length != 7) throw new ArgumentError("An array with exactly 7 elements is needed");
			_abbreviatedDayNames = pDayNames.concat();
			_upperAbbrDays = _toUpperArray(_abbreviatedDayNames);
		}
		
		public function get dayNames():Array {
			return _dayNames.concat();
		}
		
		public function set dayNames(pDayNames:Array):void {
			if (pDayNames == null) throw new ArgumentError();
			if (pDayNames.length != 7) throw new ArgumentError("An array with exactly 7 elements is needed");
			_dayNames = pDayNames.concat();
			_upperDays = _toUpperArray(_dayNames);
		}
		
		public function get abbreviatedMonthNames():Array {
			return _abbreviatedMonthNames.concat();
		}
		
		public function set abbreviatedMonthNames(pMonthNames:Array):void {
			if (pMonthNames == null) throw new ArgumentError();
			if (pMonthNames.length != 7) throw new ArgumentError("An array with exactly 13 elements is needed");
			_abbreviatedMonthNames = pMonthNames.concat();
			_upperAbbrMonths = _toUpperArray(_abbreviatedMonthNames);
		}
		
		public function get monthNames():Array {
			return _monthNames.concat();
		}
		
		public function set monthNames(pMonthNames:Array):void {
			if (pMonthNames == null) throw new ArgumentError();
			if (pMonthNames.length != 13) throw new ArgumentError("An array with exactly 13 elements is needed");
			_monthNames = pMonthNames.concat();
			_upperMonths = _toUpperArray(_monthNames);
		}
		
		public function get fullDateTimePattern():String {
			return _fullDateTimePattern == null ? longDatePattern + " " + longTimePattern : _fullDateTimePattern;
		}
		
		public function set fullDateTimePattern(pPattern:String):void {
			_fullDateTimePattern = pPattern;
		}
		
		public static function get currentInfo():DateTimeFormatInfo {
			return Culture.currentCulture.datetimeFormat;
		}
		
		public static function get invariantInfo():DateTimeFormatInfo {
			if (_invInfo == null){
				_invInfo = new DateTimeFormatInfo();
			}
			
			return _invInfo;
		}
		
		public function get firstDayOfWeek():uint {
			return _firstDayOfWeek;
		}
		
		public function set firstDayOfWeek(pFdow:uint):void {
			if (pFdow < 0 || pFdow > 6) throw new RangeError();
			_firstDayOfWeek = pFdow;
		}
		
		public function get rfc1123Pattern():String {
			return _RFC1123Pattern;
		}
		
		public function get sortableDateTimePattern():String {
			return _sortableDateTimePattern;
		}
		
		public function get universalSortableDateTimePattern():String {
			return _universalSortableDateTimePattern;
		}
		
		public function getAbbreviatedDayName(pDow:int):String {
			if (pDow < 0 || pDow > 6) throw new RangeError();
			return abbreviatedDayNames[pDow];
		}
		
		public function get shortestDayNames():Array {
			return _shortDayNames.concat();
		}
		
		public function set shortestDayNames(pDayNames:Array):void {
			if (pDayNames == null) throw new ArgumentError();
			if (pDayNames.length != 7) throw new ArgumentError("An array with exactly 7 elements is needed");
			_shortDayNames = pDayNames.concat();
		}
		
		public function getShortestDayName(pDow:int):String {
			if (pDow < 0 || pDow > 6) throw new RangeError();
			return _shortDayNames[pDow];
		}
		
		public function get nativeCalendarName():String {
			return _nativeCName;
		}
		
		public function getAllDateTimePatterns():Array {
			return  [
            	monthDayPattern,
                yearMonthPattern,
                shortDatePattern,
                shortTimePattern,
                longDatePattern,
                longTimePattern,
                fullDateTimePattern,
                rfc1123Pattern,
                sortableDateTimePattern,
                universalSortableDateTimePattern
			];
		}
		
		internal function getAbbrMonthIndex(_value:String):int {
			return _upperAbbrMonths.indexOf(_value.toUpperCase());
	    }

	    internal function getMonthIndex(_value:String):int {
			return _upperMonths.indexOf(_value.toUpperCase());
	    }

	    internal function getDayIndex (_value:String):int {
	        return _upperDays.indexOf(_value.toUpperCase());
	    }

	    internal function getAbbrDayIndex(_value:String):int {
	        return _upperAbbrDays.indexOf(_value.toUpperCase());
		}
		
		private function _toUpperArray(_arr:Array):Array {
	        var _result:Array = new Array();
	        for (var i:int = 0, il:int = _arr.length; i < il; i++) {
	            _result[i] = _arr[i].toUpperCase();
	        }

	        return _result;
		}
	}
}
